package birdsVSZombiesServer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;

import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.joints.MouseJoint;
import org.jbox2d.dynamics.joints.MouseJointDef;

import birdsVSZombiesClient.GUI.BVZButton;
import birdsVSZombiesClient.GUI.BVZRectButton;

import com.esotericsoftware.kryonet.Client;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
import com.google.gson.Gson;

import fisica.FContact;
import fisica.FContactResult;
import fisica.FMouseJoint;
import fisica.FWorld;
import fisica.Fisica;

import jbox2dprocessing.compontent.*;

import processing.core.PApplet;
import processing.core.PFont;
import shared.BVZClientToServerMessages;
import shared.BVZConstants;
import shared.BVZServerToClientsMessages;
import shared.Network;

/**
 * The server class which runs the server and physical simulation.
 * @author Yang Liu
 * @version Version 0.0.4, 11/22/2010
 */
public class BirdsVSZombiesServerThread extends PApplet{
	
	//	The kyro version of server
	public Server bvzServer;
	
	public int testTimer = 0;
	
	/*
	 * Variables for Server Status
	 */
	public int serverStat = INITCONNECTED;
	public static final int INITCONNECTED = 0;
	public static final int INITPHYS = 1;
	public static final int RUNPHYS = 2;
	
	/*
	 * Variables for Player Information
	 */
	public BVZPlayer[] bvzPlayerList = new BVZPlayer[2];
	public int bvzPlayerNum = 0;
	
	/*
	 * Variables for physical stuff
	 */
	public LinkedList<BaseBox> boxes;
	public LinkedList<BoundaryBox> boundaries;
	public LinkedList<BaseCircle> circles;
	
	public MouseInteractionOrg[] mouseJoints = new MouseInteractionOrg[2];
	public MouseInteractionOrg[] mouseJointsOrg = new MouseInteractionOrg[2];
	
	//	Physical stuff from fisica
	public FWorld world;
	
	public FMouseJoint slingJoint;
	public boolean isSlingReleased = false;
	public int queueingBirdID = 0;
	public int attachBirdDelay = 2000;
	public Timer schedule;
	public boolean tryPlayingSchedule = false;
	
	/*
	 * Variables for Json Messages
	 */
	public Gson gson;
	public BVZClientToServerMessages incomingMessage;
	public BVZServerToClientsMessages outcomingMessage;
	
	/*
	 * Server GUI
	 */
	public BVZRectButton startButton;
	public PFont f;
	
	
	public void setup(){
		size(BVZConstants.GAMEWINDOWWIDTH, BVZConstants.GAMEWINDOWHEIGHT);
		frameRate(BVZConstants.GAMEFRAMERATE);
		
		//	Set up the server
		bvzServer = new Server();
		bvzServer.start();
		try {
			bvzServer.bind(Network.TCPport, Network.UDPport);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		Network.register(bvzServer);
		
		bvzServer.addListener(new Listener(){
			public void received(Connection c, Object obj){
				if(obj instanceof BVZClientToServerMessages){
					incomingMessage = (BVZClientToServerMessages)obj;
					switch (incomingMessage.clientStat) {
						case BVZConstants.CLIENTCONNECTED:
							BVZServerToClientsMessages msg = new BVZServerToClientsMessages(BVZConstants.SERVERWELCOMEMESSAGE);
							c.sendTCP(msg);
							break;
						case BVZConstants.CLIENTJOININ:
							makeBVZPlayer(incomingMessage.playerRole);
							if(bvzPlayerNum == 2)
								callRunASimplePhys();
							break;
						case BVZConstants.CLIENTRUNNINGPHYS:
							switch (incomingMessage.mouseStat) {
								case BVZConstants.MOUSEPRESSED:
									createMouseJoint();
									break;
								case BVZConstants.MOUSEDRAGGED:
									updateMouseJoint();
									break;
								case BVZConstants.MOUSERELEASED:
									destoryMouseJoint();
									break;
								default:
									break;
							}
							
							break;
						default:
							break;
					}

				}
			}
		});
		
		f = createFont("Arial", 20, true);
		
		//	Init the fisica physical world
		Fisica.init(this);
		world = new FWorld();
		world.setEdges();
		world.setGravity(0, 1000);

		
		AbstractBox2DShape.parent = this;
		AbstractBox2DShape.world = this.world;
		MouseInteraction.world = this.world;
		MouseInteractionOrg.world = this.world;
		BVZButton.parent = this;
		BVZServerToClientsMessages.parent = this;
		
		gson = new Gson();
		
//		startButton = new BVZRectButton("Click to start!");
	}
	
	public void draw(){
		background(255);
		smooth();
		
		serverSelfUpdate();
		
//		startButton.display();
//		startButton.update();
	}
	
	
	/**
	 * Update server according to different server status.
	 */
	public void serverSelfUpdate(){
		switch (serverStat) {
			case INITCONNECTED:
				break;
			case INITPHYS:
				initPhysWorld();
				break;
			case RUNPHYS:
				runPhys();
				break;
			default:
				break;
		}
	}
	
	
	/**
	 * Change the server status to start the physical simulation.
	 */
	public void callRunASimplePhys(){
		serverStat = INITPHYS;
		
	}
	
	/**
	 * Initialize the physical world.
	 */
	public void initPhysWorld(){
		
		boundaries = new LinkedList<BoundaryBox>();
		boxes = new LinkedList<BaseBox>();
		circles = new LinkedList<BaseCircle>();
		
		//	Initial the environment 
		BoundaryBox bottomBoundary = new BoundaryBox(600, 550, 1200, 100, BVZConstants.NOROLE);
		boundaries.add(bottomBoundary);
				
//		BoundaryBox bottomBoundary3 = new BoundaryBox(0, 250, 50, 500, BVZConstants.NOROLE);
//		boundaries.add(bottomBoundary3);
//		
//		BoundaryBox bottomBoundary4 = new BoundaryBox(1200, 250, 50, 500, BVZConstants.NOROLE);
//		boundaries.add(bottomBoundary4);
		
		//	Initial the birds shooter
		NormalBird nb = new NormalBird( 200, 350);
		BlackBird bb = new BlackBird( 200, 400);
		WhiteBird wb = new WhiteBird( 150, 400);
		ToughBird tb = new ToughBird( 130, 400);
		circles.add(nb);
		circles.add(bb);
		circles.add(wb);
		circles.add(tb);
		
		// Initial the sling
		
		slingJoint = new FMouseJoint(circles.get(queueingBirdID).baseCircle, circles.get(queueingBirdID).baseCircle.getX(), circles.get(queueingBirdID).baseCircle.getY());
		
		slingJoint.setDamping(0.3f);
		slingJoint.setFrequency(100);
		slingJoint.setAnchor(0, 0);
		slingJoint.setMaxForce(1000.0f);
		
		world.add(slingJoint);
		
		schedule = new Timer();
		
		
//		MouseJointDef md = new MouseJointDef();
//		md.body1 = world.getGroundBody();
//		md.body2 = nb.baseCircle.getBox2DBody();
//		
//		Vec2 mp = Fisica.screenToWorld(200, 200);
//		md.target.set(mp);
//		
//		md.maxForce = 1000.0f * nb.baseCircle.getBox2DBody().m_mass;
//		md.frequencyHz = 20.0f;
//		md.dampingRatio = 0.9f;
//		
//		nb.baseCircle.getBox2DBody().wakeUp();
//		
//		slingJoint = (MouseJoint) world.createJoint(md);
		
		
		
		
		//	Initial the zombies defender
		NormalZombie nz1 = new NormalZombie( 900.0f, 300.0f);
		circles.add(nz1);
		PunkZombie nz2 = new PunkZombie( 950.0f, 300.0f);
		circles.add(nz2);
		JacksonZombie nz3 = new JacksonZombie( 850.0f, 300.0f);
		circles.add(nz3);
		
		WoodBlockSmall wbs1 = new WoodBlockSmall( 1000.0f, 300.0f);
		boxes.add(wbs1);
		WoodBlockLarge wbs2 = new WoodBlockLarge( 800.0f, 360.0f);
		boxes.add(wbs2);
		StoneBlockFrame wbs5 = new StoneBlockFrame( 850.0f, 360.0f);
		boxes.add(wbs5);
		IceBlockMedium wbs6 = new IceBlockMedium( 900.0f, 400.0f);
		boxes.add(wbs6);
		IceBlockFrame wbs7 = new IceBlockFrame( 900.0f, 120.0f);
		boxes.add(wbs7);

		
		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(BVZConstants.SERVERPHYSINITCOMPLETED);
		String msgJson = gson.toJson(msg) + "*";
		bvzServer.sendToAllTCP(msg);
		
//		physicalSim.start();
		
		serverStat = RUNPHYS;
	}
	
	/**
	 * Run a simple physical simulation and send data to Clients.
	 */
	public void runPhys(){
//		box2d.step();
		
		world.step();
		world.draw();
		
		//	Draw the helper shapes on the server. Optional.
		for (int i = 0; i < boundaries.size(); i++) {
			BoundaryBox bb = boundaries.get(i);
			bb.display();
		}
		
		//		Display the PhysCircles
		for (int i = 0; i < circles.size(); i++) {
			BaseCircle c = (BaseCircle)circles.get(i);
			c.display();
		}
		
		for (int i = 0; i < boxes.size(); i++) {
			BaseBox b = (BaseBox)boxes.get(i);
			b.display();
		}
		
		shootTheBirds();
		
//		slingJoint.setTarget(200, 200);
		
		//		Make a snap shot of the scene
		this.testTimer++;
		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(0, BVZConstants.SERVERRUNPHYS, boundaries, circles, boxes);
				
		bvzServer.sendToAllTCP(msg);
//		println(msgJson);
	}
		
	/**
	 * Add players when a new client connected.
	 * @param bvzClient The clients which the current player is in.
	 */
	public void makeBVZPlayer(int playerRole){
		BVZPlayer bvzPlayer = new BVZPlayer(playerRole);
		bvzPlayerList[playerRole] = bvzPlayer;
		
		bvzPlayerNum++;
	}
	
	/**
	 * Construct the message the server will send back to clients
	 */
	public void makeMessages(){
		// TODO make this function
	}
	
	/**
	 * Create a mouse joint only when the shape is belong to the player.
	 */
	public void createMouseJoint(){
		mouseJoints[incomingMessage.playerRole] = new MouseInteractionOrg();
		if(mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage) != null && mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage).bodyMaster == incomingMessage.playerRole){
			mouseJoints[incomingMessage.playerRole].bind(incomingMessage.mousePosX, incomingMessage.mousePosY, mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage));
			mouseJoints[incomingMessage.playerRole].update(incomingMessage.mousePosX, incomingMessage.mousePosY);
			println("Mouse Joint Created!!!!");
		}
	}
	
	/**
	 * Update a mouse joint
	 */
	public void updateMouseJoint(){
		mouseJoints[incomingMessage.playerRole].update(incomingMessage.mousePosX, incomingMessage.mousePosY);
//		println("Mouse Joint updated!!!");
	}
	
	/**
	 * Destroy a mouse joint
	 */
	public void destoryMouseJoint(){
		mouseJoints[incomingMessage.playerRole].destory();
		
//		slingJoint.releaseGrabbedBody();
//		slingJoint = null;
		if(incomingMessage.playerRole == BVZConstants.BIRDSSHOOTER){
			isSlingReleased = true;
		}
		
		
		println("Mouse Joint Destoried!!!!");
	}
	
	/**
	 * Shoot the bird when release the sling
	 */
	public void shootTheBirds(){
		if(isSlingReleased){
			Vec2 birdPos = new Vec2(slingJoint.getGrabbedBody().getX(), slingJoint.getGrabbedBody().getY());
			Vec2 slingTarget =  new Vec2(slingJoint.getTargetX(), slingJoint.getTargetY());
			
			float dst = birdPos.sub(slingTarget).length();
			
			if(dst <= 30){
				
				slingJoint.releaseGrabbedBody();
//				world.remove(slingJoint);
				queueingBirdID++;
				if(queueingBirdID <= 3){
					schedule.schedule(new AttachToSling(), attachBirdDelay);
				}
				
				
				
				isSlingReleased = false;
			}
			
		}
	}
	
	public class AttachToSling extends TimerTask{
		public AttachToSling() {
			println("New TimerTask started.");
			slingJoint.setGrabbedBodyAndTarget(circles.get(queueingBirdID).baseCircle, 200, 350);
		}

		@Override
		public void run() {
//			world.add(slingJoint);

			println("Attached");
			circles.get(queueingBirdID).baseCircle.setPosition(200, 350);
//			schedule.cancel();
			
		}
	}
	
	//	Attach the birds to the sling
	public void attachBirds(){
		
	}
	
	// For collision detection
	
	public void contactStarted(FContact contact){
		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(1, BVZConstants.SERVERRUNPHYS, boundaries, circles, boxes);
		bvzServer.sendToAllTCP(msg);
	}
	
	 public void contactResult(FContactResult result) {
	   // Draw an ellipse where the contact took place and as big as the normal impulse of the contact
		 
//		 fill(0, 255, 0);
//		 ellipse(result.getX(), result.getY(), result.getNormalImpulse(), result.getNormalImpulse());
//		 if(result.getNormalImpulse() > 5000){
//			 println("body1: " +result.getBody1().getClass().getName());
//			 println("body2: " +result.getBody2().getClass().getName());
//			 println(result.getBody1().getContactsResults().size());
//		 }

	   // Trigger your sound here
	   // ...
	 }

//	
//	void contactPersisted(FContact contact) {
//	   // Draw in blue an ellipse where the contact took place
//	   fill(0, 0, 170);
//	   ellipse(contact.getX(), contact.getY(), 10, 10);
//	 }
	 
	 public void contactEnded(FContact contact){
//		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(1, BVZConstants.SERVERRUNPHYS, boundaries, circles, boxes);
//		println("ddd");
//		bvzServer.sendToAllTCP(msg);
	 }
	
	public static void main(String _args[]) {
		PApplet.main(new String[] { birdsVSZombiesServer.BirdsVSZombiesServerThread.class.getName() });
	}
}






